import json
import sys
import shutil
import os
import time

ROOT_CLS_NAME = 'Root'
DATA_FIELD = 'data'

SPACE = ' '
DART_CLASS_KEYWORD = 'class'
IOS_CLASS_KEYWORD = '@interface'
nodes = []
member_map = {}
depth = 0
dart = True
# TBD
java = False
ios = False
gen_class_map = {}
class_key_word = DART_CLASS_KEYWORD
TAB_4_SPACES = '  ' if dart else '    '

base_response = 'OdianBaseResponse'
base_response_data = 'OdianBaseResponseData'

target_class_name = 'TestRsp'
target_data_class_name = 'TestRspData'
target_path = ''
target_rsp_type = 1


class SimpleNode:
    def __init__(self):
        self.name = ''
        self.depth = 0


class Node(SimpleNode):
    def __init__(self):
        super(Node, self).__init__()
        self.class_name = ''
        self.parent = None
        self.map = {}

    def info(self):
        return '[' + str(self.depth) + ':' + self.name \
               + '->' + self.parent.name + ':' + str(self.parent.depth) + '] = ' + str(self.map)


def parse_type(data):
    data_type = 'dynamic' if dart else 'Object' if java else 'NSObject'
    if isinstance(data, str):
        data_type = 'String' if not ios else 'NSString*'
    elif isinstance(data, bool):
        data_type = 'bool' if dart else 'Boolean' if java else 'BOOL'
    elif isinstance(data, float):
        data_type = 'double'
    elif isinstance(data, int):
        data_type = 'int'
    elif isinstance(data, list):
        if ios:
            data_type = 'NSMutableArray*'
        elif len(data) > 0:
            elem = data[0]
            list_elem_type = ''
            if isinstance(elem, str) or isinstance(elem, float) or isinstance(elem, int) or isinstance(elem, bool):
                list_elem_type = '<' + parse_type(elem) + '>'
            data_type = 'List' + list_elem_type
        else:
            data_type = 'List<dynamic>' if dart else 'List'
    elif isinstance(data, dict):
        data_type = 'dynamic' if dart else 'Object' if java else 'NSObject'

    return data_type


def add_node_info(data, name, parent):
    global depth
    node = Node()
    node.name = name
    if depth == 1 and name == DATA_FIELD:
        node.class_name = target_data_class_name
    else:
        node.class_name = str(name)[:1].upper() + str(name)[1:] + 'Class'
    simple_node = SimpleNode()
    simple_node.depth = depth
    simple_node.name = parent
    node.parent = simple_node
    depth = depth + 1
    node.depth = depth
    has_invalid_field_name = False
    for k in data.keys():
        node.map[k] = parse_type(data[k])
        # 变量命名只能使用：字母 数字 $ _
        # 变量第一个字符只能使用：字母 $ _
        # 变量第一个字符不能使用：数字
        # 不能只使用关键字，但是可以包含关键字
        if str(k).isdigit() or str(k[0]).isdigit() or str(k).__contains__('-'):
            has_invalid_field_name = True
            print(k + ' can not use as field name\n')
    # print(node.info())
    if has_invalid_field_name:
        node.class_name = 'Map<String, dynamic>' if dart else 'Map<String, Object>' if java else 'NSMutableDictionary*'
    nodes.append(node)


def parse_command(data, list_name, mem_parent):
    global nodes
    global member_map
    global depth

    if len(nodes) == 0 or list_name is not None:
        add_node_info(data,
                      list_name if list_name is not None else ROOT_CLS_NAME,
                      mem_parent if mem_parent is not None else '')

    for key in data.keys():
        value = data[key]
        if isinstance(value, dict):
            add_node_info(value, key, mem_parent)
            parse_command(value, None, key)
        elif isinstance(value, list):
            if len(value) == 0:
                print(key + ' is empty list')
            elif isinstance(value[0], dict):
                parse_command(value[0], key, mem_parent if list_name is None else list_name)
            else:
                print(key + ' is basic type list')
    depth = depth - 1


def filter_same_node():
    for node in nodes:
        if (node.class_name not in gen_class_map) and \
                str(node.class_name).lower().__contains__(str(node.name).lower()):
            if len(gen_class_map) == 0:
                gen_class_map[node.class_name] = node
            else:
                exist_same = False
                for key in gen_class_map.keys():
                    item = gen_class_map[key].map
                    if len(item.keys()) == len(node.map.keys()):
                        diff = item.keys() - node.map.keys()
                        if len(diff) == 0:
                            # same keys
                            print(node.class_name + ' has same class keys with ' + key)
                            node.class_name = key
                            exist_same = True
                            break
                if not exist_same:
                    gen_class_map[node.class_name] = node


def gen_class_info():
    for key in gen_class_map.keys():
        print('class ' + key + ' {\n')
        item = gen_class_map[key].map
        for field in item.keys():
            update_type = False
            for k in gen_class_map.keys():
                if field == gen_class_map[k].name:
                    field_type = gen_class_map[k].class_name
                    if item[field] == 'List':
                        field_type = 'List<' + field_type + '>'
                    update_type = True
                    print(TAB_4_SPACES + field_type + SPACE + field + ';\n')
                    break

            if not update_type:
                field_type = item[field]
                for node in nodes:
                    if node.name == field and (node.parent is not None) and \
                            (node.parent.name.upper() == key.replace('Class', '').upper()):
                        field_class_name = node.class_name
                        if item[field] == 'List':
                            field_type = 'List<' + field_class_name + '>'
                        break
                print(TAB_4_SPACES + field_type + SPACE + field + ';\n')
        print('}\n')


def save_info_to_file(file, msg):
    file.write(msg + '\n')


def save_dart_file():
    global base_response
    global base_response_data
    global target_data_class_name

    target = open(target_class_name + '.dart', 'w')
    # 写入import信息
    save_info_to_file(target, 'import \'package:json_annotation/json_annotation.dart\';')
    if target_rsp_type == 1:
        # 欧电
        save_info_to_file(target, 'import \'package:babyhealth/api/odian/OdianBaseResponse.dart\';')
        save_info_to_file(target, 'import \'package:babyhealth/api/odian/OdianBaseResponseData.dart\';')
    else:
        # CYT
        base_response = 'IvyBabyResponse'
        base_response_data = ''
        save_info_to_file(target, 'import \'package:ivybaby_api/api/base/base_ivybaby_response.dart\';')
    save_info_to_file(target, 'part \'' + target_class_name + '.g.dart\';')

    # 循环写入文件类相关信息
    for info in gen_class_map.keys():
        node = gen_class_map[info]
        # json序列化注解
        save_info_to_file(target, '\n@JsonSerializable()')
        class_name = node.class_name
        # 类名及继承类信息
        if DATA_FIELD in node.map and node.depth == 1:
            class_name = target_class_name
            # 主类（继承自base_response）
            data_class_name = target_data_class_name
            # 主类中的data的类型（默认为target_data_class_name，若data是List类型，则重新设置）
            if node.map[DATA_FIELD] == 'List':
                # 子元素为对象的List，其子元素类型用target_data_class_name定义
                data_class_name = 'List<' + data_class_name + '>'
            elif str(node.map[DATA_FIELD]).startswith('List'):
                # 子元素为基本类型的List
                data_class_name = node.map[DATA_FIELD]
            elif node.map[DATA_FIELD] != 'dynamic':
                data_class_name = node.map[DATA_FIELD]
            save_info_to_file(target, 'class' + SPACE + target_class_name + SPACE + 'extends' + SPACE
                              + base_response + '<' + data_class_name + '>' + SPACE + '{')
        elif node.name == DATA_FIELD and node.depth == 2:
            # data类（继承自base_response_data，仅处理根节点中的data字段，用depth约束）
            extend_data_info = ''
            if len(str(base_response_data)) > 0:
                extend_data_info = 'extends' + SPACE + base_response_data + SPACE
            save_info_to_file(target, 'class' + SPACE + node.class_name + SPACE + extend_data_info + '{')
        else:
            # 其他类
            save_info_to_file(target, 'class' + SPACE + node.class_name + SPACE + '{')

        # 默认构造方法
        save_info_to_file(target, TAB_4_SPACES + class_name + '() : super();\n')

        # 类成员字段（root类中成员信息已经在base_response中定义，不需要再添加）
        if node.depth > 1:
            for field in node.map.keys():
                field_type = node.map[field]
                update = False
                for key in gen_class_map.keys():
                    if field == gen_class_map[key].name:
                        update = True
                        field_type = gen_class_map[key].class_name
                        if node.map[field] == 'List':
                            field_type = 'List<' + field_type + '>'
                        break
                if not update:
                    for child in nodes:
                        if child.name == field and (child.parent is not None) and \
                                child.depth - 1 == node.depth:
                            field_type = child.class_name
                            if node.map[field] == 'List':
                                field_type = 'List<' + field_type + '>'
                            break
                save_info_to_file(target, TAB_4_SPACES + field_type + SPACE + field + ';\n')

        # json 序列化方法
        save_info_to_file(target, TAB_4_SPACES + 'factory ' + class_name +
                          '.fromJson(Map<String, dynamic> json) => _$' +
                          class_name + 'FromJson(json);\n')
        save_info_to_file(target, TAB_4_SPACES + 'Map<String, dynamic> toJson() => _$' +
                          class_name + 'ToJson(this);\n}')

    # 关闭文件，若指定路径则移动到指定路径
    target.close()
    if len(target_path) > 0:
        current_path = os.path.abspath(__file__)
        father_path = os.path.abspath(os.path.dirname(current_path) + os.path.sep + ".")
        shutil.move(father_path + os.path.sep + target_class_name + '.dart', target_path)


def save_yxy_android():
    global base_response
    global base_response_data
    global target_data_class_name

    base_response = 'APIBaseRequest'
    base_response_data = 'BaseResponseData'
    target = open(target_class_name + '.java', 'w')
    # 写入package信息
    if len(target_path) > 0:
        package_info = str(target_path).find('com')
        package_info = str(target_path)[package_info:].replace('/', '.')
        print('package_info ' + package_info)
        save_info_to_file(target, 'package ' + package_info + ';\n')

    # 写入import信息
    save_info_to_file(target, 'import com.drcuiyutao.lib.api.APIBaseRequest;')
    save_info_to_file(target, 'import com.drcuiyutao.lib.api.BaseResponseData;')
    save_info_to_file(target, '\nimport java.io.Serializable;')
    save_info_to_file(target, 'import java.util.List;\n')

    # 循环写入文件类相关信息
    for info in gen_class_map.keys():
        node = gen_class_map[info]
        # 类名及继承类信息
        if DATA_FIELD in node.map and node.depth == 1:
            # 主类（继承自base_response）
            data_class_name = target_data_class_name
            # 主类中的data的类型（默认为target_data_class_name，若data是List类型，则重新设置）
            if node.map[DATA_FIELD] == 'List':
                # 子元素为对象的List，其子元素类型用target_data_class_name定义
                data_class_name = 'List<' + data_class_name + '>'
            elif str(node.map[DATA_FIELD]).startswith('List'):
                # 子元素为基本类型的List
                data_class_name = node.map[DATA_FIELD]
            else:
                data_class_name = target_class_name + '.' + data_class_name
            save_info_to_file(target, 'public class ' + target_class_name + ' extends ' +
                              base_response + '<' + data_class_name + '> {')
        elif node.name == DATA_FIELD and node.depth == 2:
            # data类（继承自base_response_data，仅处理根节点中的data字段，用depth约束）
            save_info_to_file(target, TAB_4_SPACES + 'public static class ' + node.class_name +
                              ' extends ' + base_response_data + SPACE + '{')
        else:
            # 其他类
            save_info_to_file(target, TAB_4_SPACES + 'public static class ' + node.class_name +
                              ' implements Serializable {')

        # 类成员字段（root类中成员信息已经在base_response中定义，不需要再添加）
        if node.depth > 1:
            for field in node.map.keys():
                field_type = node.map[field]
                update = False
                for key in gen_class_map.keys():
                    if field == gen_class_map[key].name:
                        update = True
                        field_type = gen_class_map[key].class_name
                        if node.map[field] == 'List':
                            field_type = 'List<' + field_type + '>'
                        break
                if not update:
                    for child in nodes:
                        if child.name == field and (child.parent is not None) and \
                                child.depth - 1 == node.depth:
                            field_type = child.class_name
                            if node.map[field] == 'List':
                                field_type = 'List<' + field_type + '>'
                            break
                save_info_to_file(target, TAB_4_SPACES + TAB_4_SPACES + 'private ' + field_type + SPACE + field + ';')
            save_info_to_file(target, TAB_4_SPACES + '}')

    save_info_to_file(target, '}')
    # 关闭文件，若指定路径则移动到指定路径
    target.close()
    if len(target_path) > 0:
        current_path = os.path.abspath(__file__)
        father_path = os.path.abspath(os.path.dirname(current_path) + os.path.sep + ".")
        shutil.move(father_path + os.path.sep + target_class_name + '.java', target_path)


if __name__ == '__main__':
    if len(sys.argv) > 1:
        # Response体对应类名，同时作为文件名
        target_class_name = sys.argv[1]
        if len(sys.argv) > 2:
            # Response体中data对应类名
            target_data_class_name = sys.argv[2]
            if len(sys.argv) > 3:
                # 生成文件到指定路径，路径未指定到话，默认是生成在当前路径下
                target_path = sys.argv[3]
                if len(sys.argv) > 4:
                    # 是否使用欧电结构，默认为使用欧电。为0时使用cyt api结构
                    target_rsp_type = int(sys.argv[4])
        print(sys.argv)

    with open('data.json', 'r', encoding='utf-8') as f:
        start_time = time.time()
        content = f.read()
        content = json.loads(content)
        parse_command(content, None, ROOT_CLS_NAME)
        filter_same_node()
        # gen_class_info()
        if dart:
            save_dart_file()
        elif java:
            save_yxy_android()
        end_time = time.time()
        cost = int(round(end_time * 1000 - start_time * 1000))
        print("程序运行时间：%.8s ms" % cost)
